import json

from django.shortcuts import render

# Create your views here.
from rest_framework import viewsets, status
from rest_framework.response import Response
from rest_framework.exceptions import AuthenticationFailed
# from rest_framework.pagination import PageNumberPagination
from django.db.models import Q, Count

from datetime import datetime, date, timezone
import traceback
# 使用 reduce 函数来简化嵌套字典的创建过程
# from functools import reduce

from apps.homepage.models import ZtStory, ZtModule, ZtProject, ZtProjectProduct
# from .serializers import ZtStoryDetailSerializer
from apps.homepage.views import ZtStatsViewSet

import time
from functools import wraps

def timing_decorator(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        start_time = time.time()
        result = func(*args, **kwargs)
        end_time = time.time()
        print(f"Function {func.__name__!r} executed in {(end_time - start_time)*1000:.2f}ms")
        return result
    return wrapper

#判断是否登录
def has_permission(request):

    # session没有token，则未登录
    session_token = request.session.get('token')
    if not session_token:
        return False

    # 如果未传入token，则未登录
    request_token = request.META.get('HTTP_TOKEN')
    if not request_token:
        return False

    #如果都传入，则比对是否一致
    if session_token == request_token:
        return True
    else:
        return False

#查询一个产品是否对应多个项目，如果是需要筛选模块（一个项目对应多个产品，默认这个项目包含所有产品的模块）
# @timing_decorator
def if_select_module(project_id):
    #查询所有项目
    projects = ZtProject.objects.filter(project=0, deleted='0').values('id').distinct()
    projects = [project['id'] for project in projects]

    #查询所有项目与产品的对应表
    product_projectSet_dict = {}  # 产品对应的项目字典
    project_productNum = set()  #项目对应的产品集合

    project_products = ZtProjectProduct.objects.all().values('project', 'product').distinct()

    #计算指定项目对应的产品的项目数量
    for project_product in project_products:
        product = project_product['product']
        project = project_product['project']

        # 项目对应的产品集合
        if project == project_id:
            project_productNum.add(product)

        #产品对应的项目集合
        if product not in product_projectSet_dict.keys():
            product_projectSet_dict[product] = set()
        if project in projects:
            product_projectSet_dict[product].add(project)

    #如果项目没有对应的产品（实际不可能存在）或者没有该项目
    if len(project_productNum) == 0:
        return False
    elif len(project_productNum) == 1:
        product_projectSet = product_projectSet_dict[list(project_productNum)[0]]
        #如果产品只有一个项目，也不需要筛选
        if len(product_projectSet) == 1:
            return False
        else:
            return True
    else:
        #如果一个项目对应多个产品，不筛选模块
        return False

# 计算需求、设计、实施、验收的状态
def cal_status(story_id ,story_status, addStage, stage, is_overdue, _type='story', done_tests=None):
    _status = '未开始'  # 默认为未开始

    #如果阶段为空，则设置为需求
    if not addStage:
        addStage = 'story'

    #需求的状态计算
    if _type == 'story':

        if story_status == 'active':
            # 如果评审通过，则需求和设计都完成
            _status = '已完成'
        elif addStage == 'design':
            # 如果评审不通过，但是阶段为设计，则设计进行中、需求完成
            _status = '已完成'
        elif addStage == 'story':
            # 如果评审不通过，但是阶段为需求，则需求进行中
            _status = '进行中'
            if is_overdue:
                _status = '超期告警'
        else:
            _status = '未开始'
            if is_overdue:
                _status = '超期告警'
    # 设计的状态计算
    elif _type == 'design':

        if story_status == 'active':
            # 如果评审通过，则需求和设计都完成
            _status = '已完成'
        elif addStage == 'design':
            # 如果评审不通过，但是阶段为设计，则设计进行中、需求完成
            _status = '进行中'
            if is_overdue:
                _status = '超期告警'
        else:
            _status = '未开始'
            if is_overdue:
                _status = '超期告警'
    # 验收的状态计算
    elif _type == 'implement':
        # 进行中,完成测试但还没发布（验收）
        if story_id in done_tests:
            if stage != 'released':
                _status = '进行中'
                if is_overdue:
                    _status = '超期告警'
            else:
                # 已完成，完成测试且发布
                _status = '已完成'
        else:
            if is_overdue:
                _status = '超期告警'

    # 验收的状态计算
    elif _type == 'acceptance':
        if stage == 'released':
            _status = '已完成'
        else:
            if is_overdue:
                _status = '超期告警'

    return _status


#所有需求甘特图数据
class GanttListViewSet(viewsets.ViewSet):

    # cal_status = cal_status

    def initial(self, request, *args, **kwargs):
        super(GanttListViewSet, self).initial(request, *args, **kwargs)
        # 检查用户是否登录
        if not has_permission(request):
            # 如果用户未登录，抛出 AuthenticationFailed 异常
            raise AuthenticationFailed({'code': 0, 'msg': '未登录'})

    #甘特图数据
    # 使用装饰器
    @timing_decorator
    def gantt(self, request, *args, **kwargs):
        '''
        根据项目id、功能模块id查询对应的需求
        '''
        try:
            #获取接口类型
            api_type = kwargs.get('type')

            # 如果type没有传
            if not api_type:
                return Response({'code': 0, 'msg': '没有携带接口类型的标识'}, status=status.HTTP_400_BAD_REQUEST)

            #如果type不在指定范围中，只允许需求、设计、实施和验收
            if api_type not in ['story', 'design', 'implement', 'acceptance']:
            # if api_type not in ['story', 'design', 'acceptance']:
                return Response({'code': 0, 'msg': 'api_type只允许以下四种：story、design、implement、acceptance'}, status=status.HTTP_400_BAD_REQUEST)

            #获取参数
            project_id = self.request.query_params.get('project_id', None)
            module_select_id = self.request.query_params.get('module_id', None)

            #如果project_id没有传
            if not project_id:
                return Response({'code': 0, 'msg': '没有携带项目id'}, status=status.HTTP_400_BAD_REQUEST)

            # if module_id:
            #     #先获取包含该id的所有模块(父子节点)
            #     modules = ZtModule.objects.filter(path__icontains=module_id, deleted='0')
            # else:
            #获取该项目所有模块
            root_ids = set()
            # 查询项目及对应的迭代（迭代也可以建模块）
            query_project = Q(id=project_id) | Q(project=project_id)
            query_project = Q(deleted='0') & query_project
            project_excutions = ZtProject.objects.filter(query_project).values('id').distinct()
            for project_excution in project_excutions:
                root_ids.add(project_excution['id'])

            # 查询项目对应的产品
            project_products = ZtProjectProduct.objects.filter(project__in=root_ids).values('product').distinct()
            for project_product in project_products:
                root_ids.add(project_product['product'])

            # 获取该项目所有模块
            query_module = Q()
            # query_module &= Q(root__projectProduct_product__project_id=project_id)  # 根据项目id查询模块
            # query_module &= Q(root__projectProduct_product__project__deleted='0')  # 项目不能被删除
            # 使用括号来确保“或”条件与之前的“与”条件组合
            query_module &= Q(root__in=root_ids)
            query_module &= Q(deleted='0')  # 模块不能被删除

            modules = ZtModule.objects.filter(query_module).values('id', 'name', 'path').distinct()

            #处理模块将,151,152,改为,738,151,152,
            #获取所有的位尾节点模块
            model_path_end = {}
            for module in modules:
                path = module['path'].strip(',').split(',')
                if len(path)>1:
                    model_path_end[path[-1]] = {'id':module['id'], 'path':module['path']}

            #处理首节点对应尾巴节点的情况
            for i,module in enumerate(modules):
                path = module['path'].strip(',').split(',')
                if len(path)>1 and path[0] in model_path_end.keys():
                    pre_path = model_path_end[path[0]]
                    path = pre_path['path']+ ''.join(path[1:])+','
                    module['path'] = path

            #排序，将父模块放到前面
            modules = sorted(modules, key=lambda x:len(x['path'].split(',')))

            #整理模块树结构
            module_tree, path_map = self.module_to_tree(modules)
            # with open('1.json','w') as f:
            #     json.dump(module_tree,f, ensure_ascii=False)
            # print(path_map)

            # 搜索条件
            # 初始化一个空的查询集
            query = Q()
            query &= Q(projectStory_story__project__id=project_id)  # 根据项目id查询需求
            query &= Q(projectStory_story__project__deleted='0')  # 项目不能被删除
            query &= Q(deleted='0')  # 需求不能被删除
            query &= Q(type='story')  # 确保为研发需求
            # query &= Q(module__id__in=path_map.keys())  # 对应模块的需求

            # stories = ZtStory.objects.filter(query).exclude(status='closed').select_related('assignedTo')\
            #     .values('id', 'title', 'parent','status', 'addStage', 'module', 'stage', 'assignedTo','assignedTo__realname','end','begin').distinct()
            stories = ZtStory.objects.filter(query).select_related('assignedTo') \
                .values('id', 'title', 'parent', 'status', 'addStage', 'module', 'stage', 'assignedTo',
                        'assignedTo__realname', 'end', 'begin').distinct()

            # _status = '未开始'  #默认为未开始
            # 梳理父子结构
            story_tree = {}
            name_map = {}
            # parent_status = {}

            # 如果是实施和发布，则将开始结束日期修改为项目开始结束日期
            if api_type in ['implement', 'acceptance']:
                # 查询一下项目是否为产品性项目，1为是
                project = ZtProject.objects.filter(id=project_id, project=0).distinct().first()

                if not project:
                    return Response({'code': 0, 'msg': '该项目id不存在'})

            # 如果是实施则获取完成测试的需求id
            done_tests = None
            if api_type == 'implement':
                stats_clss = ZtStatsViewSet()
                done_tests = stats_clss.test_stats(project_id, only_done=True)

            for story in stories:

                # 获取用户名和真实姓名
                if story['assignedTo'] != '' and story['assignedTo'] != 'closed':
                    assignedTo_account = story['assignedTo']
                    assignedTo_realname = story['assignedTo__realname']
                else:
                    assignedTo_account = ''
                    assignedTo_realname = ''

                name_map[assignedTo_account] = assignedTo_realname


                # 暂时没有开始结束日期，我先默认指定一下
                # story['begin'] = date(2024, 9, 3)
                # story['end'] = date(2024, 9, 5)

                # 如果是实施和发布，则将开始结束日期修改为项目开始结束日期
                if api_type in ['implement', 'acceptance']:
                    story['begin'] = project.begin
                    story['end'] = project.end

                    #如果结束日期-开始时间超过5年，则将结束时间置空
                    if story['end'].year - story['begin'].year > 5:
                        story['end'] = None

                # 判断给定的日期时间是否已经超期（即是否在当前日期时间之前）
                if story['end']:
                    is_overdue = story['end'] < datetime.now(timezone.utc).date()
                else:
                    is_overdue = False

                #计算状态
                _status = cal_status(story['id'], story['status'], story['addStage'], story['stage'], is_overdue, _type=api_type, done_tests=done_tests)

                if api_type == 'acceptance' and _status != '已完成':
                    continue

                # if story['status'] == 'active':
                #     # 如果评审通过，则需求和设计都完成
                #     _status = '已完成'
                # elif story['addStage'] == 'design':
                #     # 如果评审不通过，但是阶段为设计，则设计进行中、需求完成
                #     _status = '已完成'
                # elif story['addStage'] == 'story':
                #     # 如果评审不通过，但是阶段为需求，则需求进行中
                #     _status = '进行中'
                #     if is_overdue:
                #         _status = '超期告警'
                # else:
                #     _status = '未开始'
                #     if is_overdue:
                #         _status = '超期告警'

                # 模块id
                if story['module'] in path_map.keys():
                    module_id = story['module']
                    module_path = path_map[module_id]
                else:
                    module_id = 0
                    module_path = ',0,'

                story_node = {
                    "id": 'story-{}'.format(story['id']),
                    "module": module_id,
                    'name': story['title'],
                    'status': _status,
                    'begin': '' if story['begin'] is None else story['begin'].strftime('%Y-%m-%d'),
                    'end': '' if story['end'] is None else story['end'].strftime('%Y-%m-%d'),
                    'assignedTo': assignedTo_realname,
                    'module_path': module_path,
                    'type': api_type,
                    'child':{}
                }
                #如果parent不为0或-1，则需要将其加入父节点中
                # 如果parent不为0或-1，则需要将其加入父节点中，统计父需求的所有指派人
                if story['parent'] == 0 or story['parent'] == -1:
                    story_node['assignedTo_account'] = set()
                    if assignedTo_account != '':
                        story_node['assignedTo_account'].add(assignedTo_account)
                    story_tree[story['id']] = story_node
                else:

                    # 如果没有父节点（实际没有这种情况）
                    if story['parent'] in story_tree.keys():
                        if assignedTo_account != '':
                            story_tree[story['parent']]['assignedTo_account'].add(assignedTo_account)
                        story_tree[story['parent']]['child'][story['id']] = story_node
                    else:
                        story_node['assignedTo_account'] = set()
                        if assignedTo_account != '':
                            story_node['assignedTo_account'].add(assignedTo_account)
                        story_tree[story['id']] = story_node

            #将需求挂载到模块树上
            module_tree = self.mount_stories_to_modules(module_tree, story_tree, path_map)

            # 将模块为0的放到根节点上
            for story_id, story in story_tree.items():
                if story['module'] == 0:
                    module_tree['story{}'.format(story_id)] = story

            # 更新父节点和模块的状态
            module_tree = self.update_tree_status(module_tree, name_map=name_map)

            # 如果选了模块，则根据_type和_id条件进行筛选
            if module_select_id:
                filter_data = {'type': 'module', 'id': module_select_id}
                module_tree = self.get_modules_by_select(module_tree, filter_data)

            #转为列表形式
            module_tree_list = list(module_tree.values())

            #如果该项目对应的产品有多个项目，则需要筛选模块
            # if if_select_module(int(project_id)):
                # 筛选有需求的模块
                # st = time.time()
            module_tree_list = self.remove_empty_child_modules(module_tree_list)
                # if module_tree_list is None:
                #     module_tree_list = []
                # print((time.time()-st)*1000,'ms')

            # return Response({'code': 1, 'msg': '获取成功', 'data': list(module_tree.values())})
            return Response({'code': 1, 'msg': '获取成功', 'data': module_tree_list})
        except:
            print(traceback.format_exc())
            return Response({'code': 0, 'msg': '获取失败'},status=status.HTTP_500_INTERNAL_SERVER_ERROR)

    # 列表数据
    # 使用装饰器
    @timing_decorator
    def list(self, request, *args, **kwargs):
        '''
        根据项目id、功能模块id、需求id查询对应的需求,还可根据名称、状态、指派人进行搜索
        '''
        try:

            # 获取接口类型
            api_type = kwargs.get('type')

            # 如果type没有传
            if not api_type:
                return Response({'code': 0, 'msg': '没有携带接口类型的标识'}, status=status.HTTP_400_BAD_REQUEST)

            # 如果type不在指定范围中，只允许需求、设计、实施和验收
            if api_type not in ['story', 'design', 'implement', 'acceptance']:
            # if api_type not in ['story', 'design', 'acceptance']:
                return Response({'code': 0, 'msg': 'api_type只允许以下四种：story、design、implement、acceptance'},
                                status=status.HTTP_400_BAD_REQUEST)

            # 获取项目等参数
            project_id = self.request.query_params.get('project_id', None)
            _id = self.request.query_params.get('id', None)
            _type = self.request.query_params.get('type', None)
            module_path = self.request.query_params.get('module_path', None)

            # 如果project_id, _id, _type, path没有传
            if not (project_id and _id and _type and module_path):
                return Response({'code': 0, 'msg': '缺少参数'}, status=status.HTTP_400_BAD_REQUEST)

            #模块根节点
            root_path = module_path.strip(',').split(',')[0]

            #获取搜索参数
            filter_name = self.request.query_params.get('name', None)
            filter_status = self.request.query_params.get('status', None)
            filter_assignedTo = self.request.query_params.get('assignedTo', None)

            # if module_id:
            #     #先获取包含该id的所有模块(父子节点)
            #     modules = ZtModule.objects.filter(path__icontains=module_id, deleted='0')
            # else:
            # 获取该项目所有模块
            root_ids = set()
            # 查询项目及对应的迭代（迭代也可以建模块）
            query_project = Q(id=project_id) | Q(project=project_id)
            query_project = Q(deleted='0') & query_project
            project_excutions = ZtProject.objects.filter(query_project).values('id').distinct()
            for project_excution in project_excutions:
                root_ids.add(project_excution['id'])

            # 查询项目对应的产品
            project_products = ZtProjectProduct.objects.filter(project__in=root_ids).values('product').distinct()
            for project_product in project_products:
                root_ids.add(project_product['product'])

            # 获取该项目所有模块
            query_module = Q()
            # query_module &= Q(root__projectProduct_product__project_id=project_id)  # 根据项目id查询模块
            # query_module &= Q(root__projectProduct_product__project__deleted='0')  # 项目不能被删除
            # 使用括号来确保“或”条件与之前的“与”条件组合
            query_module &= Q(root__in=root_ids)
            query_module &= Q(deleted='0')  # 模块不能被删除

            modules = ZtModule.objects.filter(query_module).values('id', 'name', 'path').distinct()

            # 处理模块将,151,152,改为,738,151,152,
            # 获取所有的位尾节点模块
            model_path_end = {}
            for module in modules:
                path = module['path'].strip(',').split(',')
                if len(path) > 1:
                    model_path_end[path[-1]] = {'id': module['id'], 'path': module['path']}

            # 处理首节点对应尾巴节点的情况
            for i, module in enumerate(modules):
                path = module['path'].strip(',').split(',')
                if len(path) > 1 and path[0] in model_path_end.keys():
                    pre_path = model_path_end[path[0]]
                    path = pre_path['path'] + ''.join(path[1:]) + ','
                    module['path'] = path

            # 排序，将父模块放到前面
            modules = sorted(modules, key=lambda x: len(x['path'].split(',')))

            # 整理模块树结构
            module_tree, path_map = self.module_to_tree(modules, mode='list')
            # print(path_map)

            # 搜索条件
            # 初始化一个空的查询集
            query = Q()
            query &= Q(projectStory_story__project__id=project_id)  # 根据项目id查询需求
            query &= Q(projectStory_story__project__deleted='0')  # 项目不能被删除
            query &= Q(deleted='0')  # 需求不能被删除
            # query &= Q(module__id__in=path_map.keys())  # 对应模块的需求
            query &= Q(type='story')  # 确保为研发需求

            # stories = ZtStory.objects.filter(query).exclude(status='closed').select_related('assignedTo', 'module').distinct()
            stories = ZtStory.objects.filter(query).select_related('assignedTo','module').distinct()

            # 梳理父子结构
            story_tree = {}
            team = set()
            name_map = {}   #account和realname映射
            # parent_status = {}

            # 转换关系
            category_map = {
                'feature': '功能',
                'interface': '接口',
                'performance': '性能',
                'safe': '安全',
                'experience': '体验',
                'improve': '改进',
                'other': '其他',
            }

            # 如果是实施和发布，则将开始结束日期修改为项目开始结束日期
            if api_type in ['implement', 'acceptance']:
                # 查询一下项目是否为产品性项目，1为是
                project = ZtProject.objects.filter(id=project_id, project=0).distinct().first()

                if not project:
                    return Response({'code': 0, 'msg': '该项目id不存在'})

            #如果是实施则获取完成测试的需求id
            done_tests = None
            if api_type == 'implement':
                stats_clss = ZtStatsViewSet()
                done_tests = stats_clss.test_stats(project_id, only_done=True)

            for story in stories:
                #获取用户名和真实姓名
                assignedTo = story.assignedTo

                if assignedTo:
                    assignedTo_account = assignedTo.account
                    team.add(assignedTo_account)
                    assignedTo_realname = assignedTo.realname
                else:
                    assignedTo_account = ''
                    assignedTo_realname = ''

                name_map[assignedTo_account] = assignedTo_realname

                # 暂时没有开始结束日期，我先默认指定一下
                # story.begin = date(2024, 9, 3)
                # story.end = date(2024, 9, 5)

                #如果是实施和发布，则将开始结束日期修改为项目开始结束日期
                if api_type in ['implement', 'acceptance']:
                    story.begin = project.begin
                    story.end = project.end

                # 判断给定的日期时间是否已经超期（即是否在当前日期时间之前）
                if story.end:
                    is_overdue = story.end < datetime.now(timezone.utc).date()
                else:
                    is_overdue = False

                # 计算状态
                _status = cal_status(story.id ,story.status, story.addStage, story.stage, is_overdue, _type=api_type, done_tests=done_tests)

                # if story.status == 'active':
                #     # 如果评审通过，则需求和设计都完成
                #     _status = '已完成'
                # elif story.addStage == 'design':
                #     # 如果评审不通过，但是阶段为设计，则设计进行中、需求完成
                #     _status = '已完成'
                # elif story.addStage == 'story':
                #     # 如果评审不通过，但是阶段为需求，则需求进行中
                #     _status = '进行中'
                #     if is_overdue:
                #         _status = '超期告警'
                # else:
                #     _status = '未开始'
                #     if is_overdue:
                #         _status = '超期告警'

                # 模块id
                if story.module.id in path_map.keys():
                    module_id = story.module.id
                else:
                    module_id = 0

                story_node = {
                    "id": 'story-{}'.format(story.id),
                    "module": module_id,
                    'name': story.title,
                    'status': _status,
                    'category': category_map[story.category],
                    'pri': story.pri,
                    'begin': '' if story.begin is None else story.begin.strftime('%Y-%m-%d'),
                    'end': '' if story.end is None else story.end.strftime('%Y-%m-%d'),
                    'assignedTo': assignedTo_realname,
                    'type': api_type,
                    'child': {},
                }

                # 如果parent不为0或-1，则需要将其加入父节点中，统计父需求的所有指派人
                if story.parent == 0 or story.parent == -1:
                    story_node['assignedTo_account'] = set()
                    if assignedTo_account != '':
                        story_node['assignedTo_account'].add(assignedTo_account)
                    story_tree[story.id] = story_node
                else:

                    #如果没有父节点（实际没有这种情况）
                    if story.parent in story_tree.keys():
                        if assignedTo_account != '':
                            story_tree[story.parent]['assignedTo_account'].add(assignedTo_account)
                        story_tree[story.parent]['child'][story.id] = story_node
                    else:
                        story_node['assignedTo_account'] = set()
                        if assignedTo_account != '':
                            story_node['assignedTo_account'].add(assignedTo_account)
                        story_tree[story.id] = story_node

            # 将需求挂载到模块树上
            module_tree = self.mount_stories_to_modules(module_tree, story_tree, path_map)

            # 更新父节点和模块的状态
            module_tree = self.update_tree_status(module_tree, name_map=name_map)

            #根据_type和_id条件进行筛选
            filter_data = {'type': _type, 'id': _id}
            module_tree_select = self.get_modules_by_select(module_tree, filter_data)

            #如果有搜索条件
            if filter_assignedTo or filter_status or filter_name:
                filter_data = {'name': filter_name, 'status': filter_status, 'assignedTo': filter_assignedTo}
                module_tree_select = self.get_modules_by_select(module_tree_select, filter_data)
            #     return Response({'code': 1, 'msg': '获取成功', 'data': list(module_tree_select.values())})
            # else:
            return Response({'code': 1, 'msg': '获取成功', 'data': {'teamCount': len(team), 'list':list(module_tree_select.values())}})
        except:
            print(traceback.format_exc())
            return Response({'code': 0, 'msg': '获取失败'}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)

    # # 将路径整理成树结构的函数
    # # 使用装饰器
    # # @timing_decorator
    def module_to_tree(self, modules, mode='gantt'):

        # 初始化树结构
        tree = {}

        #模块_路径对应表
        path_map = {}

        # 遍历路径列表，构建树结构
        for module in modules:
            path_map[module['id']] = module['path']
            # 移除路径两端的逗号，并分割成步骤列表
            steps = module['path'].strip(',').split(',')

            # 递归地构建或更新树结构
            current_level = tree
            for step in steps:
                if step:  # 确保步骤不为空
                    if step not in current_level:
                        if mode == 'gantt':
                            current_level[step] = {'id':'module-{}'.format(module['id']),
                                                   'name':module['name'],
                                                   'status':'未开始',
                                                   'begin':'',
                                                   'end':'',
                                                   'assignedTo': '',
                                                   'assignedTo_account': '',
                                                   'module_path': module['path'],
                                                   'type': "module",
                                                   'child': {}}
                        elif mode == 'list':
                            current_level[step] = {'id': 'module-{}'.format(module['id']),
                                                   'name': module['name'],
                                                   'status': '未开始',
                                                   'category': "",
                                                   'pri': "",
                                                   'begin': '',
                                                   'end': '',
                                                   'assignedTo': '',
                                                   'assignedTo_account': '',
                                                   'type': "module",
                                                   'child': {}}


                    current_level = current_level[step]['child']
        return tree, path_map

    #将需求挂载到模块下面
    # 使用装饰器
    # @timing_decorator
    def mount_stories_to_modules(self, module_tree, story_tree, path_map):

        #根据给定的路径在模块树中递归查找模块。
        def find_module_by_path(modules, path):
            if not path or not modules:
                return None
            current_id = path.pop(0)
            if current_id in modules:
                if not path:
                    return modules[current_id]
                else:
                    return find_module_by_path(modules[current_id]['child'], path)
            return None

        for story_id, story in story_tree.items():

            # 如果模型为0，即未挂载模块，则不挂载到模块下面
            if story['module'] == 0:
                continue

            # 如果模块不存在，则跳过
            if story['module'] not in path_map.keys():
                continue

            # 路径列表
            module_path = path_map[story['module']].strip(',').split(',')

            target_module = find_module_by_path(module_tree, module_path)
            if target_module:
                # if 'child' not in target_module:
                #     target_module['child'] = {}
                if 'child' in target_module:
                    target_module['child']['story'+str(story_id)] = story
        return module_tree

    #更新树的状态及日期
    # 使用装饰器
    # @timing_decorator
    def update_tree_status(self, tree, name_map={}):

        #更新状态和日期
        def update_parent_status(node):
            # 获取子节点字典
            children = node.get('child', {})

            # 如果没有子节点，则不更新状态
            if not children:

                # 将字段转为列表
                node['child'] = list(node['child'].values())
                return

            # 初始化状态集合
            status_set = set()
            begin_list = []
            end_list = []
            assignedTo_set = set()

            # 遍历所有子节点
            for child_node in children.values():
                # 递归更新子节点的状态
                update_parent_status(child_node)
                #子节点状态集合
                status_set.add(child_node['status'])

                #日期
                if child_node['begin'] != '':
                    begin_list.append(child_node['begin'])
                if child_node['end'] != '':
                    end_list.append(child_node['end'])

                #指派人
                if 'assignedTo_account' in child_node.keys():
                    if child_node['assignedTo_account']:
                        #如果是集合，则转为字符串
                        if isinstance(child_node['assignedTo_account'], set):
                            child_node['assignedTo_account'] = ';'.join(child_node['assignedTo_account'])

                        if ';' not in child_node['assignedTo_account']:
                            assignedTo_set.add(child_node['assignedTo_account'])
                            # child_node['assignedTo'] = name_map[child_node['assignedTo_account']]
                            # del child_node['assignedTo_account']
                        else:
                            assignedTo_list = child_node['assignedTo_account'].split(';')
                            assignedTo_set |= set(assignedTo_list)
                            # child_node['assignedTo'] = ';'.join([name_map[i] for i in assignedTo_list])

                    del child_node['assignedTo_account']

            #只有为模块才需要更新日期和指派人
            if node['type'] == 'module' and begin_list:
                node['begin'] = min(begin_list)
            if node['type'] == 'module' and end_list:
                node['end'] = max(end_list)

            #将用户名转为姓名然后删除用户名
            if node['type'] == 'module' and assignedTo_set:
                node['assignedTo_account'] = ';'.join(assignedTo_set)
                if ';' not in node['assignedTo_account']:
                    node['assignedTo'] = name_map[node['assignedTo_account']]
                else:
                    assignedTo_list = node['assignedTo_account'].split(';')
                    node['assignedTo'] = ';'.join([name_map[i] for i in assignedTo_list])

            # 如果全是未开始，则为未开始
            if status_set == set(['未开始']):
                node['status'] = '未开始'
            # 如果全为超期，则为超期
            elif status_set == set(['超期告警']):
                node['status'] = '超期告警'
            # 如果全为完成，则为完成
            elif status_set == set(['已完成']):
                node['status'] = '已完成'
            # 其余均为进行中
            else:
                node['status'] = '进行中'

            #将字段转为列表
            node['child'] = list(node['child'].values())

        #更新状态和日期
        for _, module in tree.items():
            update_parent_status(module)

        #删除根节点的assignedTo_account
        for key in tree.keys():
            del tree[key]['assignedTo_account']

        return tree

    # 根据id获取指定id的父节点和子节点
    # def get_module_with_parents(self, tree, module_id):
    #     # 递归函数，用于获取指定module_id的节点及其所有父节点
    #     def retrieve_module(node, module_id):
    #         # 如果当前节点是目标模块，则返回当前节点
    #         # if node['id'] in module_id:
    #         if node['status'] == '已完成':
    #             return node
    #
    #         # 遍历子节点列表
    #         for child_node in node.get('child', []):
    #             # 递归搜索子节点
    #             result = retrieve_module(child_node, module_id)
    #             # 如果在子节点中找到目标模块，则将子节点添加到当前节点的child中，并返回当前节点
    #             if result is not None:
    #                 node['child'] = [result]
    #                 return node
    #         # 如果当前节点及其子节点中都没有找到目标模块，则返回None
    #         return None
    #
    #     # 遍历整棵树，寻找目标模块及其父节点
    #     for mod_node in tree.values():
    #         module_data = retrieve_module(mod_node, module_id)
    #         if module_data is not None:
    #             return module_data
    #     return None  # 如果没有找到对应的模块，则返回None

    #根据条件进行筛选
    # 使用装饰器
    # @timing_decorator
    def get_modules_by_select(self, tree, filter_data):

        # 递归函数，用于遍历树并找到所有匹配status和name的模块及其所有子节点
        def traverse_and_filter(node):

            #默认为True，不匹配直接返回
            judge = True
            for key, value in filter_data.items():
                judge = judge and (value is None or value in str(node[key]))
                if not judge:
                    break

            # 如果当前节点匹配status和name，则保留该节点及其所有子节点
            if judge:
                # 直接返回当前节点及其所有子节点
                return node.copy()

            # 如果当前节点不匹配，继续遍历子节点
            filtered_children = []
            for child_node in node.get('child', []):
                child_result = traverse_and_filter(child_node)
                if child_result:
                    filtered_children.append(child_result)

            # 如果子节点中有匹配的，则保留当前节点，包含所有子节点
            if filtered_children:
                filtered_node = node.copy()
                filtered_node['child'] = filtered_children
                return filtered_node

            # 如果没有匹配的子节点，返回None
            return None

        # 遍历整棵树，应用筛选条件
        filtered_tree = {}
        for mod_id, mod_node in tree.items():
            result = traverse_and_filter(mod_node)
            if result:
                filtered_tree[mod_id] = result

        return filtered_tree

    # 删除模块的child为空的情况
    # @timing_decorator
    def remove_empty_child_modules_node(self, data):
        # If the data itself is empty, return None
        if not data:
            return None
        # with open('1.json','w') as f:
        #     json.dump(data,f,ensure_ascii=False)
        # Check if the current node is a module
        if data['type'] == 'module':
            # Recursively remove empty modules from children
            data['child'] = [self.remove_empty_child_modules_node(child) for child in data['child']]
            # Filter out None values from the list after recursion
            data['child'] = [child for child in data['child'] if child is not None]
            # If after removing children, the module has no children left, remove it
            if not data['child']:
                return None
        return data

    def remove_empty_child_modules(self,data):
        # Process each item in the list
        return [self.remove_empty_child_modules_node(item) for item in data if
                self.remove_empty_child_modules_node(item) is not None]

    # def remove_empty_child_modules(self, data):
    #     # 默认有空
    #     if_have_null = True
    #     # 循环到直到没有空
    #     while if_have_null:
    #         new_data = []
    #         cnt = 0
    #         for item in data:
    #             if item['type'] == 'module' and not item['child']:
    #                 if_have_null = True
    #                 cnt += 1
    #                 continue
    #             else:
    #                 if 'child' in item:
    #                     item['child'] = self.remove_empty_child_modules(item['child'])
    #                 new_data.append(item)
    #         if cnt == 0:
    #             if_have_null = False
    #         data = new_data
    #
    #     return new_data

# #需求父节点或子节点数据
# class ZtStoryListViewSet(viewsets.ModelViewSet):
#
#     queryset = ZtStory.objects
#     serializer_class = ZtStoryListSerializer
#
#     def initial(self, request, *args, **kwargs):
#         super(ZtStoryListViewSet, self).initial(request, *args, **kwargs)
#         # 检查用户是否登录
#         if not has_permission(request):
#             # 如果用户未登录，抛出 AuthenticationFailed 异常
#             raise AuthenticationFailed({'code': 0, 'msg': '未登录'})
#
#     #格式化数据
#     def list(self, request, *args, **kwargs):
#         '''
#         根据项目id、功能模块id、需求id、需求名称、需求类别、需求指派人查询对应的需求
#         '''
#         try:
#             #获取参数
#             project_id = self.request.query_params.get('project_id', None)
#             module_id = self.request.query_params.get('module_id', None)
#             story_id = self.request.query_params.get('story_id', None)
#             story_name = self.request.query_params.get('story_name', None)
#             story_category = self.request.query_params.get('story_category', None)
#             story_assignedTo = self.request.query_params.get('story_assignedTo', None)
#
#             #如果传了需求id，那就是去找该id的子节点
#             if story_id:
#                 queryset = self.queryset.filter(parent=story_id, deleted='0').exclude(status='closed').distinct()
#
#                 # 序列化为字典格式
#                 stories = self.get_serializer(queryset, many=True).data
#                 res = []
#
#                 for story in stories:
#
#                     # 暂时没有开始结束日期，我先默认指定一下
#                     story['begin'] = date(2024, 9, 3)
#                     story['end'] = date(2024, 9, 5)
#
#                     # 状态
#                     _status = self.judge_status(story)
#
#                     story_node = {
#                         "id": story['id'],
#                         'name': story['title'],
#                         'module': story['module_name'],
#                         'category': story['category'],
#                         'pri': story['pri'],
#                         'begin': story['begin'].strftime("%Y-%m-%d"),
#                         'end': story['end'].strftime("%Y-%m-%d"),
#                         'assignedTo': story['assignedTo_realname'],
#                         'status': _status,
#                         'child': 0
#                     }
#
#                     res.append(story_node)
#
#                 return Response({'code': 1, 'msg': '获取成功','data': res})
#
#             #如果project_id没有传
#             if not project_id:
#                 return Response({'code': 0, 'msg': '没有携带项目id'}, status=status.HTTP_400_BAD_REQUEST)
#
#             # 根据搜索条件查询
#             # 初始化一个空的查询集
#             query = Q()
#             query &= Q(projectStory_story__project__id=project_id)  # 根据项目id查询需求
#             query &= Q(projectStory_story__project__deleted='0')  # 项目不能被删除
#             query &= Q(deleted='0')  # 需求不能被删除
#
#             #模块id查询module_id查询条件
#             if module_id:
#                 # 先获取包含该id的所有模块(父子节点)
#                 modules = ZtModule.objects.filter(path__icontains=module_id)
#                 query &= Q(module__id__in=modules)
#
#             # 指派人查询story_assignedTo查询条件
#             if story_assignedTo:
#                 query &= Q(assignedTo__realname__icontains=story_assignedTo)
#
#             # 需求名称查询story_name查询条件
#             if story_name:
#                 query &= (Q(title__icontains=story_name) | Q(module__name=story_name))
#
#             # 需求类别查询story_category查询条件
#             if story_category:
#                 query &= Q(category=story_category)
#
#             queryset = self.queryset.filter(query).exclude(status='closed').distinct()
#
#             #序列化为字典格式
#             stories = self.get_serializer(queryset, many=True).data
#
#             #统计需要的所有父节点（不包含parent为0的需求）
#             parent_child_ids = []
#             for story in stories:
#                 parent = story['parent']
#                 if parent == -1:
#                     parent_child_ids.append(story['id'])
#                 elif parent != 0 and parent not in parent_child_ids:
#                     parent_child_ids.append(parent)
#
#             #查询需要的父节点以及父节点对应的子节点
#             parent_child_stories = ZtStory.objects.filter(Q(parent__in=parent_child_ids) | Q(id__in=parent_child_ids))\
#                 .values('id', 'title', 'parent', 'status', 'addStage').distinct()
#
#             #对搜索父节点和子节点的状态进行计算
#             parent_child_status = {}
#             parent_status = {}
#
#             for story in parent_child_stories:
#                 # 暂时没有开始结束日期，我先默认指定一下
#                 story['begin'] = date(2024, 9, 3)
#                 story['end'] = date(2024, 9, 5)
#
#                 # 状态
#                 _status = self.judge_status(story)
#
#                 parent_child_status[story['id']] = {'status':_status}
#                 parent_child_status[story['id']]['have_parent'] = story['title']
#
#                 if story['parent'] != -1:
#                     # 统计父节点下的子节点状态
#                     if story['parent'] not in parent_status.keys():
#                         parent_status[story['parent']] = set()
#                     parent_status[story['parent']].add(_status)
#
#                     #包含子节点无父节点的名字
#                     parent_child_status[story['id']]['no_parent'] = \
#                         '{}/{}'.format(parent_child_status[story['parent']]['have_parent'], story['title'])
#
#             # 遍历父节点，根据子节点状态计算父节点状态
#             for key, value in parent_status.items():
#                 # 如果全是未开始，则为未开始
#                 if value == set(['未开始']):
#                     parent_child_status[key]['status'] = '未开始'
#                 # 如果只有完成和超期或者全为超期，则为超期
#                 elif value == set(['超期告警']):
#                     parent_child_status[key]['status'] = '超期告警'
#                 # 如果全为完成，则为完成
#                 elif value == set(['已完成']):
#                     parent_child_status[key]['status'] = '已完成'
#                 # 其余均为进行中
#                 else:
#                     parent_child_status[key]['status'] = '进行中'
#
#             #排序，确保父模块在子模块前面
#             print([i['model_path'] for i in stories])
#             stories = sorted(stories, key=lambda x: self.extract_number(x['model_path']))
#             print([i['model_path'] for i in stories])
#             # 梳理父子结构
#             story_tree = {}
#             team = set()
#             res = {}
#
#             for story in stories:
#
#                 module_paths = [str(i) for i in story['module_path'].split(',')[1:-1]]
#                 # if len(module_paths) == 1:
#                 #     res[story['module_id']] = {'id':story['module_id'],'name':story['module_name'],'child':{}}
#                 # else:
#                 #     # 开始遍历路径，填充值
#                 #     current_dict = res
#                 #     for step in path_steps[:-1]:  # 遍历除了最后一个元素的所有步骤
#                 #         # 确保每个步骤都有'child'键
#                 #         if 'child' not in current_dict[step]:
#                 #             current_dict[step]['child'] = {}
#                 #         current_dict = current_dict[step]['child']
#                 #
#                 #     # 在路径的最后一个元素处填充值
#                 #     current_dict[path_steps[-1]] = value_to_fill
#
#                 # 暂时没有开始结束日期，我先默认指定一下
#                 story['begin'] = date(2024, 9, 3)
#                 story['end'] = date(2024, 9, 5)
#
#                 # 如果指派人不为空，则算1个人
#                 if story['assignedTo_realname'] != '':
#                     team.add(story['assignedTo'])
#
#                 #状态
#                 if story['id'] in parent_child_status.keys():
#                     _status = parent_child_status[story['id']]['status']
#                 else:
#                     _status = self.judge_status(story)
#
#                 story_node = {
#                     "id": story['id'],
#                     'name': story['title'],
#                     'module': story['module_name'],
#                     'category': story['category'],
#                     'pri': story['pri'],
#                     'begin': story['begin'].strftime("%Y-%m-%d"),
#                     'end': story['end'].strftime("%Y-%m-%d"),
#                     'assignedTo': story['assignedTo_realname'],
#                     'status': _status,
#                     'module_id': story['module'],
#                     'model_path': story['model_path'],
#                     'child': 0
#                 }
#                 # 如果parent不为0或-1，则需要将其加入父节点中
#                 # print(story_tree)
#                 if story['parent'] == 0 or story['parent'] == -1:
#                     story_tree[story['id']] = story_node
#                 else:
#                     if story['parent'] in story_tree.keys():
#                         story_tree[story['parent']]['child'] = 1
#                     else:
#                         # 如果查出来的子节点没有父节点，则也放到搜索结果中，并且名字前+父节点
#                         story_node['name'] = parent_child_status[story['id']]['no_parent']
#                         story_tree[story['id']] = story_node
#
#             return Response({'code': 1, 'msg': '获取成功', 'data': {'teamCount': len(team), 'stories':list(story_tree.values())}})
#         except:
#             print(traceback.format_exc())
#             return Response({'code': 0, 'msg': '获取失败'}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
#
#     # 定义一个函数，用于从字符串中提取数字并转换为整数
#     def extract_number(self, s):
#         # 寻找字符串中的第一个数字
#         return int(''.join(filter(str.isdigit, s)))
#
#     def judge_status(self, story):
#
#         _status = '未开始'  # 默认为未开始
#
#         # #如果为父节点则不需要计算
#         # if story['parent'] == -1:
#         #     return _status
#
#         # 判断给定的日期时间是否已经超期（即是否在当前日期时间之前）
#         is_overdue = story['begin'] < datetime.now(timezone.utc).date()
#
#         if story['status'] == 'active':
#             # 如果评审通过，则需求和设计都完成
#             _status = '已完成'
#         elif story['addStage'] == 'design':
#             # 如果评审不通过，但是阶段为设计，则设计进行中、需求完成
#             _status = '已完成'
#         elif story['addStage'] == 'story':
#             # 如果评审不通过，但是阶段为需求，则需求进行中
#             _status = '进行中'
#             if is_overdue:
#                 _status = '超期告警'
#         else:
#             _status = '未开始'
#             if is_overdue:
#                 _status = '超期告警'
#
#         return _status

#查看需求详情
class DetailViewSet(viewsets.ViewSet):

    # queryset = ZtStory.objects
    # serializer_class = ZtStoryDetailSerializer

    def initial(self, request, *args, **kwargs):
        super(DetailViewSet, self).initial(request, *args, **kwargs)
        # 检查用户是否登录
        if not has_permission(request):
            # 如果用户未登录，抛出 AuthenticationFailed 异常
            raise AuthenticationFailed({'code': 0, 'msg': '未登录'})

    #格式化数据
    # 使用装饰器
    @timing_decorator
    def list(self, request, *args, **kwargs):
        '''
        根据需求id查询对应的父子需求，计算工时
        '''
        try:

            # 获取接口类型
            api_type = kwargs.get('type')

            # 如果type没有传
            if not api_type:
                return Response({'code': 0, 'msg': '没有携带接口类型的标识'}, status=status.HTTP_400_BAD_REQUEST)

            # 如果type不在指定范围中，只允许需求、设计、实施和验收
            if api_type not in ['story', 'design', 'implement', 'acceptance']:
            # if api_type not in ['story', 'design', 'acceptance']:
                return Response({'code': 0, 'msg': 'api_type只允许以下四种：story、design、implement、acceptance'},
                                status=status.HTTP_400_BAD_REQUEST)

            # 获取参数
            project_id = self.request.query_params.get('project_id', None)
            story_id = self.request.query_params.get('story_id', None)
            story_id = story_id.split('-')[-1]

            # 如果project_id没有传
            if not (project_id and story_id):
                return Response({'code': 0, 'msg': '缺少参数'}, status=status.HTTP_400_BAD_REQUEST)

            #如果传了需求id，那就是去找该id的父子节点
            # 合并Q对象，得到一个查询条件，它将匹配或者id为查询id或者父节点为查询id
            combined_query = Q(parent=story_id) | Q(id=story_id)

            # 添加 'deleted' 字段为0的条件
            query = combined_query & Q(deleted=0)
            query &= Q(type='story')  # 确保为研发需求

            # stories = ZtStory.objects.filter(query).exclude(status='closed').select_related('assignedTo', 'module',).prefetch_related('storySpec_story').distinct()
            stories = ZtStory.objects.filter(query).select_related('assignedTo','module', ).prefetch_related('storySpec_story').distinct()

            # 未查询到
            if not stories.exists():
                return Response({'code': 0, 'msg': '该需求id不存在'})

            # 序列化为字典格式
            # stories = self.get_serializer(queryset, many=True).data

            #初始化参数
            _status = '未开始'  #默认为未开始
            res = {}
            parent_status = set()
            cnt = 0
            team = set()

            # 如果是实施和发布，则将开始结束日期修改为项目开始结束日期
            if api_type in ['implement', 'acceptance']:
                # 查询一下项目是否为产品性项目，1为是
                project = ZtProject.objects.filter(id=project_id, project=0).distinct().first()

                if not project:
                    return Response({'code': 0, 'msg': '该项目id不存在'})

            # 如果是实施则获取完成测试的需求id
            done_tests = None
            if api_type == 'implement':
                stats_clss = ZtStatsViewSet()
                done_tests = stats_clss.test_stats(project_id, only_done=True)

            # 转换关系
            category_map = {
                'feature': '功能',
                'interface': '接口',
                'performance': '性能',
                'safe': '安全',
                'experience': '体验',
                'improve': '改进',
                'other': '其他',
            }

            # 梳理子节点状态
            for story in stories:

                # 暂时没有开始结束日期，我先默认指定一下
                # story.begin = date(2024, 9, 3)
                # story.end = date(2024, 9, 5)

                #如果是实施和发布，则将开始结束日期修改为项目开始结束日期
                if api_type in ['implement', 'acceptance']:
                    story.begin = project.begin
                    story.end = project.end

                if story.end:
                    is_overdue = story.end < datetime.now(timezone.utc).date()
                else:
                    is_overdue = False

                # 获取用户名和真实姓名
                assignedTo = story.assignedTo

                # 如果指派人不为空，则算1个人
                if assignedTo:
                    assignedTo_account = assignedTo.account
                    team.add(assignedTo_account)
                    assignedTo_realname = assignedTo.realname
                else:
                    assignedTo_realname = ''

                # 正确获取描述
                story_spec = story.storySpec_story.last()  # 获取最后一个版本的描述
                if story_spec:
                    spec = story_spec.spec  # 现在可以安全地访问spec属性
                else:
                    spec = ''

                # 计算状态
                _status = cal_status(story.id, story.status, story.addStage, story.stage, is_overdue,
                                     _type=api_type, done_tests=done_tests)

                # 获取模块名称
                module = story.module
                if module:
                    module_name = '#{} {}'.format(module.id, module.name)
                else:
                    module_name = ''

                if cnt == 0:
                    story_node = {
                        "id": story.id,
                        'name': story.title,
                        'module': module_name,
                        'category': category_map[story.category],
                        'pri': story.pri,
                        'begin': '' if story.begin is None else story.begin.strftime('%Y-%m-%d'),
                        'end': '' if story.end is None else story.end.strftime('%Y-%m-%d'),
                        'assignedTo': assignedTo_realname,
                        'status': _status,
                        'desc': spec
                    }

                    res = story_node
                    cnt += 1
                #如果parent不为0或-1，则需要将其加入父节点中
                # print(story_tree)
                if story.parent != 0 and story.parent != -1:
                    #统计父节点下的子节点状态
                    parent_status.add(res['status'])

            #根据子节点状态计算父节点状态
            if len(stories) > 1:
                #如果全是未开始，则为未开始
                if parent_status == set(['未开始']):
                    res['status'] = '未开始'
                #如果只有完成和超期或者全为超期，则为超期
                elif parent_status == set(['超期告警']):
                    res['status'] = '超期告警'
                #如果全为完成，则为完成
                elif parent_status == set(['已完成']):
                    res['status'] = '已完成'
                #其余均为进行中
                else:
                    res['status'] = '进行中'

            return Response({'code': 1, 'msg': '获取成功', 'data': {'teamCount': len(team), 'detail':res}})

        except:
            print(traceback.format_exc())
            return Response({'code': 0, 'msg': '获取失败'}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
